(* Dan Grossman, Coursera PL, HW2 Provided Code *)

(* if you use this function to compare two strings (returns true if the same
   string), then you avoid several of the functions in problem 1 having
   polymorphic types that may be confusing *)
fun same_string(s1 : string, s2 : string) =
    s1 = s2

(* put your solutions for problem 1 here *)

(*1.a*)
fun all_except_option (s: string, sl: string list) = 
	case sl of 
		[] => NONE
	|	head::tail => if same_string (s, head)
						then SOME tail
						else case all_except_option (s, tail) of 
								NONE => NONE
							|	SOME x => SOME (head::x)

(*1.b*)
fun get_substitutions1 (sll: string list list, s: string) =
	case sll of 
		[] => []
	|	head::tail => case all_except_option (s, head) of 
						SOME x => x @ get_substitutions1 (tail, s)
					|	NONE => get_substitutions1 (tail, s)

(*1.c*)
fun get_substitutions2 (sll: string list list, s: string) =
	let
		fun aux (x, acc, sub) =
			case x of
				[] => acc
			|	head::tail => case all_except_option (sub, head) of
							SOME z => aux (tail,  acc @ z, sub)
						|	NONE => aux (tail, acc, sub)
	in
		aux(sll, [], s)
	end

(*1.d*)
fun similar_names(sub: string list list, {first, middle, last}) =
	let
		val substitutions = first::get_substitutions2 (sub, first)
		fun aux (x, acc) =
			case x of
				[] => acc
			|	head::tail => aux (tail, acc @ [{first = head, last = last, middle = middle}]) 
	in
		aux (substitutions, [])
	end

(* you may assume that Num is always used with values 2, 3, ..., 10
   though it will not really come up *)
datatype suit = Clubs | Diamonds | Hearts | Spades
datatype rank = Jack | Queen | King | Ace | Num of int 
type card = suit * rank

datatype color = Red | Black
datatype move = Discard of card | Draw 

exception IllegalMove

(* put your solutions for problem 2 here *)

(*2.a*)
fun card_color (c: card) =
	case c of
		(Clubs,_) => Black
	|	(Spades,_) => Black
	|	(_,_) => Red

(*2.b*)
fun card_value (c: card ) =
	case c of
		(_, Ace) => 11
	|	(_, Num n) => n
	|	(_, _) => 10

(*2.c*)
fun remove_card (cs: card list, c: card, ex:exn ) =
	let
		fun aux (l, acc, ex) =
			case l of
				[] => raise ex
			|	head::tail => if head = c
								then acc @ tail
								else aux (tail, acc @ [head], ex)
	in
		aux(cs, [], ex)
	end


(*2.d*)
fun all_same_color (cs: card list) =
	case cs of
		[] => true
	|	head::[] => true
	|	a::b::tail => (card_color a = card_color b) andalso all_same_color (b::tail)


(*2.e*)
fun sum_cards (cs: card list) =
	let
		fun aux (x, acc) =
			case x of
				[] => acc
			|	head::tail => aux (tail, card_value (head) + acc)
	in
		aux(cs, 0)
	end

(*2.f*)
fun score (cs: card list, gl: int) =
	let
		val sum = sum_cards cs
		val res = if sum > gl
					then 3*(sum - gl)
					else gl	- sum
	in
		if all_same_color cs
			then res div 2
			else res
	end

(*2.g*)
fun officiate (cs:card list, ms:move list, gl:int) =
	let
		fun aux(cs, ms, acc) =
			case cs of
				[] => score(acc, gl)
			|	card::restcards =>  case ms of
										[] => score(acc, gl)
									|	mv::mvtl => case mv of
														Draw => if sum_cards(card::acc) > gl
																	then score(card::acc, gl)
																	else aux (restcards, mvtl, card::acc)
													|	Discard cd => aux (restcards, mvtl, remove_card(acc, cd, IllegalMove))
	in	
		aux(cs, ms, [])
	end
